package com.hz.tgb.filter.logtraceid;

import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.slf4j.MDC;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;

/**
 * 为logback日志增加traceId
 *
 * @author hezhao created on 2019/12/4 11:28
 */
@WebFilter(urlPatterns = "/*", filterName = "logMdcFilter")
@Slf4j
public class LogMdcFilter extends GenericFilterBean {

    private static final String UNIQUE_ID = "traceId";

    private static final String METHOD_POST = "POST";

    private static final String[] JSON_MEDIA_TYPE = {"application/json", "application/json;charset=UTF-8"};

    private static final int CONTENT_LENGTH = 40960;
    private static final int RESPONSE_LENGTH = 2048;

    /** 包含此种方法名不打印出参 */
    private static final String[] SEARCH_FILTER = {
            "get", "search", "page", "select", "find", "list", "detail", "login", "password"
    };
    /** 包含此种方法名不记录日志和traceId */
    private static final String[] BLACK_FILTER = {
            "swagger", "api-docs"
    };
    /** 包含此种方法名将过滤入参 */
    private static final String[] REQUEST_FILTER = {
            "login", "password"
    };
    /** 过滤入参字段列表 */
    private static final String[] REMOVE_KEY_WORD = {
            "password", "oldPassword"
    };

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        if (!(servletRequest instanceof HttpServletRequest) || !(servletResponse instanceof HttpServletResponse)) {
            throw new ServletException("Filter just supports HTTP requests");
        }
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String requestURI = request.getRequestURI();
        String method = request.getMethod();

        // 过滤监控类日志打印
        if (FilterUtils.filterOptions(request)
                || requestURI.lastIndexOf("/actuator") != -1
                || contains(BLACK_FILTER, requestURI)) {
            chain.doFilter(request, response);
            return;
        }

        Long startLong = System.currentTimeMillis();
        boolean bInsertMDC = false;

        try {
            // 生成traceId
            bInsertMDC = insertMDC();
            log.info("开始进入接口 ::: {} {}", method, requestURI);

            // 包装ServletRequest
            TraceServletRequestWrapper requestWrapper = new TraceServletRequestWrapper(request);
            // 包装ServletResponse
            TraceServletResponseWrapper responseWrapper = new TraceServletResponseWrapper(response);

            // 请求
            chain.doFilter(requestWrapper, responseWrapper);

            Long endTime = System.currentTimeMillis();
            // 跟踪请求参数
            String requestBody = traceRequestParam(requestWrapper);
            // 跟踪相应参数
            String responseBody;

            // 过滤入参
            StringBuilder builder = new StringBuilder();
            if (StringUtils.isBlank(requestBody)) {
                requestBody = "{}";
            } else {
                requestBody = filterRequestParam(requestURI, requestBody);
            }

            // 非查询方法才打印出参
            if (!contains(SEARCH_FILTER, requestURI)) {
                responseBody = traceResponseParam(responseWrapper);
                if (StringUtils.isBlank(responseBody)) {
                    responseBody = "{}";
                } else if (responseBody.length() > RESPONSE_LENGTH) {
                    responseBody = StringUtils.substring(responseBody, 0, RESPONSE_LENGTH) + "...}";
                }
            } else {
                responseBody = "{\"该API不展示响应结果\"}";
            }
            builder.append("{\"requestBody\":" + requestBody + ",");
            builder.append("\"responseBody\":" + responseBody + ",");
            builder.append("\"startTime\":" + startLong + ",");
            builder.append("\"endTime\":" + endTime + ",");
            builder.append("\"costTimes\":" + (endTime - startLong));
            builder.append("}");
            log.info("执行接口完毕 ::: {} {} \n{}", method, requestURI, builder.toString());
        } catch (Exception e) {
            log.warn("tracer request/response param error!", e);
        } finally {
            if(bInsertMDC) {
                MDC.remove(UNIQUE_ID);
            }
        }
    }

    private static String filterRequestParam(String uri, String requestBody) {
        try {
            if (StringUtils.isEmpty(uri)) {
                return requestBody;
            }
            if (contains(REQUEST_FILTER, uri)) {
                JSONObject jsonObject = JSON.parseObject(requestBody);
                for (String key : REMOVE_KEY_WORD) {
                    jsonObject.remove(key);
                }
                return jsonObject.toJSONString();
            }
            return requestBody;
        } catch (Exception e) {
            log.info("LogMdcFilter.filterRequestParam JSON解析出错", e);
        }
        return requestBody;
    }

    private static boolean contains(String[] arrays, String uri) {
        if (StringUtils.isEmpty(uri)) {
            return false;
        }
        for (String filter : arrays) {
            if (uri.toLowerCase().contains(filter.toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    private static boolean insertMDC() {
        MDC.put(UNIQUE_ID, IdUtil.fastSimpleUUID());
        return true;
    }

    private static String traceRequestParam(TraceServletRequestWrapper requestWrapper) {
        if (!FilterUtils.requestFilter(requestWrapper)) {
            return "";
        }

        String body = "";
        String method = requestWrapper.getMethod();
        String contentType = requestWrapper.getContentType();

        // 处理POST & JSON请求参数
        if (METHOD_POST.equalsIgnoreCase(method)
                && Arrays.stream(JSON_MEDIA_TYPE).anyMatch(e -> e.equalsIgnoreCase(contentType))) {
            int size = requestWrapper.getContentLength();
            if(size < CONTENT_LENGTH) {
                TraceServletInputStream traceInputStream = requestWrapper.getTraceInputStream();
                if (traceInputStream != null) {
                    body = new String(traceInputStream.getContent().getBytes(), StandardCharsets.UTF_8);
                }
            }
        } else {
            Map<String, String[]> reqParam = requestWrapper.getParameterMap();
            return JSON.toJSONString(reqParam);
        }
        return body;
    }

    private static String traceResponseParam(TraceServletResponseWrapper responseWrapper) {
        if (!FilterUtils.responseFilter(responseWrapper)) {
            return "";
        }

        TraceServletOutputStream traceOutputStream = responseWrapper.getTraceOutputStream();
        if (null != traceOutputStream && StringUtils.isNotEmpty(traceOutputStream.getContent())) {
            return new String(traceOutputStream.getContent().getBytes(), StandardCharsets.UTF_8);
        }
        return null;
    }

}
